#include <iostream>
#include <fstream>
#include <string.h>
#include <time.h>
#include <math.h>
#include "tgaimage.h"

int pack_color(int r, int g, int b, int a) {
	return ((b & 0xff) << 24) | ((g & 0xff) << 16) | ((r & 0xff) << 8) | (a & 0xff);
}


TGAImage::TGAImage() : data(NULL), width(0), height(0), bytespp(0) {
}

TGAImage::TGAImage(int w, int h, int bpp) : data(NULL), width(w), height(h), bytespp(bpp) {
	unsigned long nbytes = width*height*bytespp;
	data = new unsigned char[nbytes];
	memset(data, 0, nbytes);
}

TGAImage::TGAImage(const TGAImage &img) {
	width = img.width;
	height = img.height;
	bytespp = img.bytespp;
	unsigned long nbytes = width*height*bytespp;
	data = new unsigned char[nbytes];
	memcpy(data, img.data, nbytes);
}

TGAImage::~TGAImage() {
	if (data) delete [] data;
}

TGAImage & TGAImage::operator =(const TGAImage &img) {
	if (this != &img) {
		if (data) delete [] data;
		width  = img.width;
		height = img.height;
		bytespp = img.bytespp;
		unsigned long nbytes = width*height*bytespp;
		data = new unsigned char[nbytes];
		memcpy(data, img.data, nbytes);
	}
	return *this;
}

bool TGAImage::read_tga_file(const char *filename) {
	if (data) delete [] data;
	data = NULL;
	std::ifstream in;
	in.open (filename, std::ios::binary);
	if (!in.is_open()) {
		std::cerr << "can't open file " << filename << "\n";
		in.close();
		return false;
	}
	TGA_Header header;
	in.read((char *)&header, sizeof(header));
	if (!in.good()) {
		in.close();
		std::cerr << "an error occured while reading the header\n";
		return false;
	}
	width   = header.width;
	height  = header.height;
	bytespp = header.bitsperpixel>>3;
	if (width<=0 || height<=0 || (bytespp!=GRAYSCALE && bytespp!=RGB && bytespp!=RGBA)) {
		in.close();
		std::cerr << "bad bpp (or width/height) value\n";
		return false;
	}
	unsigned long nbytes = bytespp*width*height;
	data = new unsigned char[nbytes];
	if (3==header.datatypecode || 2==header.datatypecode) {
		in.read((char *)data, nbytes);
		if (!in.good()) {
			in.close();
			std::cerr << "an error occured while reading the data\n";
			return false;
		}
	} else if (10==header.datatypecode||11==header.datatypecode) {
		if (!load_rle_data(in)) {
			in.close();
			std::cerr << "an error occured while reading the data\n";
			return false;
		}
	} else {
		in.close();
		std::cerr << "unknown file format " << (int)header.datatypecode << "\n";
		return false;
	}
	if (!(header.imagedescriptor & 0x20)) {
		flip_vertically();
	}
	if (header.imagedescriptor & 0x10) {
		flip_horizontally();
	}
//	std::cerr << (int)header.datatypecode << "\n";
//	std::cerr << header.x_origin << "," << header.y_origin << "\n";
	std::cerr << width << "x" << height << "/" << bytespp*8 << "\n";
	in.close();
	return true;
}

bool TGAImage::load_rle_data(std::ifstream &in) {
	unsigned long pixelcount = width*height;
	unsigned long currentpixel = 0;
	unsigned long currentbyte  = 0;
	TGAColor colorbuffer;
	do {
		unsigned char chunkheader = 0;
		chunkheader = in.get();
		if (!in.good()) {
			std::cerr << "an error occured while reading the data\n";
			return false;
		}
		if (chunkheader<128) {
			chunkheader++;
			for (int i=0; i<chunkheader; i++) {
				in.read((char *)colorbuffer.raw, bytespp);
				if (!in.good()) {
					std::cerr << "an error occured while reading the header\n";
					return false;
				}
				for (int t=0; t<bytespp; t++)
					data[currentbyte++] = colorbuffer.raw[t];
				currentpixel++;
				if (currentpixel>pixelcount) {
					std::cerr << "Too many pixels read\n";
					return false;
				}
			}
		} else {
			chunkheader -= 127;
			in.read((char *)colorbuffer.raw, bytespp);
			if (!in.good()) {
				std::cerr << "an error occured while reading the header\n";
				return false;
			}
			for (int i=0; i<chunkheader; i++) {
				for (int t=0; t<bytespp; t++)
					data[currentbyte++] = colorbuffer.raw[t];
				currentpixel++;
				if (currentpixel>pixelcount) {
					std::cerr << "Too many pixels read\n";
					return false;
				}
			}
		}
	} while (currentpixel < pixelcount);
	return true;
}

bool TGAImage::write_tga_file(const char *filename, bool rle) {
	unsigned char developer_area_ref[4] = {0, 0, 0, 0};
	unsigned char extension_area_ref[4] = {0, 0, 0, 0};
	unsigned char footer[18] = {'T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.','\0'};
	std::ofstream out;
	out.open (filename, std::ios::binary);
	if (!out.is_open()) {
		std::cerr << "can't open file " << filename << "\n";
		out.close();
		return false;
	}
	TGA_Header header;
	memset((void *)&header, 0, sizeof(header));
	header.bitsperpixel = bytespp<<3;
	header.width  = width;
	header.height = height;
	header.datatypecode = (bytespp==GRAYSCALE?(rle?11:3):(rle?10:2));
	header.imagedescriptor = 0x20; // top-left origin
	out.write((char *)&header, sizeof(header));
	if (!out.good()) {
		out.close();
		std::cerr << "can't dump the tga file\n";
		return false;
	}
	if (!rle) {
		out.write((char *)data, width*height*bytespp);
		if (!out.good()) {
			std::cerr << "can't unload raw data\n";
			out.close();
			return false;
		}
	} else {
		if (!unload_rle_data(out)) {
			out.close();
			std::cerr << "can't unload rle data\n";
			return false;
		}
	}
	out.write((char *)developer_area_ref, sizeof(developer_area_ref));
	if (!out.good()) {
		std::cerr << "can't dump the tga file\n";
		out.close();
		return false;
	}
	out.write((char *)extension_area_ref, sizeof(extension_area_ref));
	if (!out.good()) {
		std::cerr << "can't dump the tga file\n";
		out.close();
		return false;
	}
	out.write((char *)footer, sizeof(footer));
	if (!out.good()) {
		std::cerr << "can't dump the tga file\n";
		out.close();
		return false;
	}
	out.close();
	return true;
}

// TODO: it is not necessary to break a raw chunk for two equal pixels (for the matter of the resulting size)
bool TGAImage::unload_rle_data(std::ofstream &out) {
	const unsigned char max_chunk_length = 128;
	unsigned long npixels = width*height;
	unsigned long curpix = 0;
	while (curpix<npixels) {
		unsigned long chunkstart = curpix*bytespp;
		unsigned long curbyte = curpix*bytespp;
		unsigned char run_length = 1;
		bool raw = true;
		while (curpix+run_length<npixels && run_length<max_chunk_length) {
			bool succ_eq = true;
			for (int t=0; succ_eq && t<bytespp; t++) {
				succ_eq = (data[curbyte+t]==data[curbyte+t+bytespp]);
			}
			curbyte += bytespp;
			if (1==run_length) {
				raw = !succ_eq;
			}
			if (raw && succ_eq) {
				run_length--;
				break;
			}
			if (!raw && !succ_eq) {
				break;
			}
			run_length++;
		}
		curpix += run_length;
		out.put(raw?run_length-1:run_length+127);
		if (!out.good()) {
			std::cerr << "can't dump the tga file\n";
			return false;
		}
		out.write((char *)(data+chunkstart), (raw?run_length*bytespp:bytespp));
		if (!out.good()) {
			std::cerr << "can't dump the tga file\n";
			return false;
		}
	}
	return true;
}

TGAColor TGAImage::get(int x, int y) {
	if (!data || x<0 || y<0 || x>=width || y>=height) {
//		std::cerr << "bad image indices for the get(" << x << "," << y << ") call - image size is (" << width << ", " << height << ")\n";
		return TGAColor();
	}
	return TGAColor(data+(x+y*width)*bytespp, bytespp);
}

bool TGAImage::set(int x, int y, TGAColor c) {
	if (!data || x<0 || y<0 || x>=width || y>=height) {
//		std::cerr << "bad image indices for the set(" << x << "," << y << ") call - image size is (" << width << ", " << height << ")\n";
		return false;
	}
//	std::cerr << x << " " << y << "\n";
	memcpy(data+(x+y*width)*bytespp, c.raw, bytespp);
	return true;
}

bool TGAImage::flip_horizontally() {
	if (!data) return false;
	int half = width>>1;
	for (int i=0; i<half; i++) {
		for (int j=0; j<height; j++) {
			TGAColor c1 = get(i, j);
			TGAColor c2 = get(width-1-i, j);
			set(i, j, c2);
			set(width-1-i, j, c1);
		}
	}
	return true;
}

bool TGAImage::flip_vertically() {
	if (!data) return false;
	unsigned long bytes_per_line = width*bytespp;
	unsigned char *line = new unsigned char[bytes_per_line];
	int half = height>>1;
	for (int j=0; j<half; j++) {
		unsigned long l1 = j*bytes_per_line;
		unsigned long l2 = (height-1-j)*bytes_per_line;
		memmove((void *)line,      (void *)(data+l1), bytes_per_line);
		memmove((void *)(data+l1), (void *)(data+l2), bytes_per_line);
		memmove((void *)(data+l2), (void *)line,      bytes_per_line);
	}
	delete [] line;
	return true;
}

bool TGAImage::scale(int w, int h) {
	if (w<=0 || h<=0 || !data) return false;
//	clock_t start, stop;
//	double t = 0.0;
//	assert((start = clock())!=-1);
	unsigned char *tdata = new unsigned char[w*h*bytespp];
//	result = TGAImage(w, h, bpp);
	int nscanline = 0;
	int oscanline = 0;
	int erry = 0;
	unsigned long nlinebytes = w*bytespp;
	unsigned long olinebytes = width*bytespp;
	for (int j=0; j<height; j++) {
		int errx = width-w;
		int nx   = -bytespp;
		int ox   = -bytespp;
		for (int i=0; i<width; i++) {
			ox += bytespp;
			errx += w;
			while (errx>=(int)width) {
				errx -= width;
				nx += bytespp;
				memcpy(tdata+nscanline+nx, data+oscanline+ox, bytespp);
			}
		}
		erry += h;
		oscanline += olinebytes;
		while (erry>=(int)height) {
			if (erry>=(int)height<<1) // it means we jump over a scanline
				memcpy(tdata+nscanline+nlinebytes, tdata+nscanline, nlinebytes);
			erry -= height;
			nscanline += nlinebytes;
		}
	}
//	stop = clock();
//	t = (double) (stop-start)/CLOCKS_PER_SEC;
//	cerr << t << "\n";
	delete [] data;
	data = tdata;
	width = w;
	height = h;
	return true;
}

template <class t> void SWAP(t &a, t &b) {
	t temp = a;
	a = b;
	b = temp;
}
//#define SWAP(a, b)  a ^= b; b ^= a; a ^= b;
#define ABS(a) ((a) < 0 ? -(a) : (a))
#define MIN(a, b) ((a)<(b)?(a):(b))
#define MAX(a, b) ((a)>(b)?(a):(b))


// TODO: calc line-image boundary intersection in order to drop excessive calculations
void TGAImage::line(int x0, int y0, int x1, int y1, TGAColor c) {
	bool steep = ABS(y1 - y0) > ABS(x1 - x0);
	if (steep) {
		SWAP(x0, y0);
		SWAP(x1, y1);
	}
	if (x0 > x1) {
		SWAP(x0, x1);
		SWAP(y0, y1);
	}
	int deltax = x1 - x0;
	int deltay = ABS(y1 - y0);
	int error = deltax>>1;
	int y = y0;
	int ystep = (y0<y1 ? 1 : -1);
	for (int x=x0; x<=x1; x++) {
		if (steep) {
			set(y, x, c);
		} else {
			set(x, y, c);
		}
		error -= deltay;
		if (error<0) {
			y += ystep;
			error += deltax;
		}
	}
}


// в buffer пишет x координаты для каждого значения y (если на 1 игрек идёт скачок на 10 иксов, то линия будет прерывистой)
bool memline(int x0, int y0, int x1, int y1, int *buffer, int bufsize) {
/*
	for (int i=0; i<bufsize; i++) {
		buffer[i] = x0 + (x1-x0)*i/(float)(bufsize);
	}
	return true;
	*/


	if (y0==y1) return false;
	int dx = ABS(x1 - x0);
	int dy = ABS(y1 - y0);
	int err = 0;
	int x = x0;
	int xstep = (x0<x1 ? 1 : -1);
	for (int i=0; i<bufsize; i++) {
		buffer[i] = x;
		err += dx;
		while (ABS(err)>dy) {
			x += xstep;
			err -= dy;
		}
	}
	return true;
}


void TGAImage::triangle(int x0, int y0, int x1, int y1, int x2, int y2, TGAColor c) {
	if (y0==y1 && y0==y2) return;
	// x0,y0 will be the top point
	// x1,y1 will be the lower left (y1 may be = to y0)
	// x2,y2 will be the lower right (y2 may be = to y0)
//	std::cerr << x0 << "," << y0 << " " << x1 << "," << y1 << " " << x2 << "," << y2 << "\n";
	if (y0>y1) {
		SWAP(x0, x1);
		SWAP(y0, y1);
	}
	if (y0>y2) {
		SWAP(x0, x2);
		SWAP(y0, y2);
	}
	if (y1>y2) {
		SWAP(x1, x2);
		SWAP(y1, y2);
	}
	int height = MAX(y2-y0, y1-y0);
	int *buf1 = new int[height]; // left boundary
	int *buf2 = new int[height]; // right boundary
	memline(x0, y0, x1, y1, buf1, y1-y0);
	memline(x0, y0, x2, y2, buf2, y2-y0);
	if (y2>y1) {
		memline(x1, y1, x2, y2, buf1+y1-y0, y2-y1);
	}
	for (int i=0; i<height; i++) {
		for (int t=MIN(buf1[i], buf2[i]); t<MAX(buf1[i], buf2[i]); t++) {
			set(t, y0+i, c);
		}
	}
	delete [] buf1;
	delete [] buf2;
}


int TGAImage::get_bytespp() {
	return bytespp;
}

int TGAImage::get_width() {
	return width;
}

int TGAImage::get_height() {
	return height;
}

bool TGAImage::convert_to_grayscale() {
	if (!width || !height || !data) {
		return false;
	}
	unsigned long nbytes = width*height;
	unsigned char *newdata = new unsigned char[nbytes];
	for (int i=0; i<width; i++) {
		for (int j=0; j<height; j++) {
			TGAColor color = get(i, j);
			newdata[i+j*width] = (color.r+color.g+color.b)/3;
		}
	}
	delete [] data;
	data = newdata;
	bytespp = GRAYSCALE;
	return true;
}

bool TGAImage::sub_rect(int x, int y, int w, int h, TGAImage &rect) {
	if (!data||x<0||y<0||w<=0||h<=0||x+w>width||y+h>height) {
		return false;
	}
	rect = TGAImage(w, h, bytespp);
	for (int i=0; i<w; i++) {
		for (int j=0; j<h; j++) {
			rect.set(i, j, get(x+i, y+j));
		}
	}
	return true;
}

bool TGAImage::canvas_size(int w, int h) {
	if (w<=0||h<=0||!data) {
		return false;
	}
	unsigned char *newdata = new unsigned char[w*h];
	for (int j=0; j<height; j++) {
		for (int i=0; i<width; i++) {
			TGAColor color = (i<w&&j<h ? get(i, j) : TGAColor(0, bytespp));
			memcpy(newdata+(i+j*w)*bytespp, color.raw, bytespp);
		}
	}
	delete [] data;
	data = newdata;
	width = w;
	height = h;
	return true;
}

int TGAImage::histogram(float hist[256]) {
	for (int i=0; i<256; i++) {
		hist[i] = 0;
	}
	for (int i=0; i<width; i++) {
		for (int j=0; j<height; j++) {
			TGAColor color = get(i, j);
			hist[GRAYSCALE==bytespp? color.val : (color.r+color.g+color.b)/3] += 1;
		}
	}
	int max_idx = -1;
	for (int i=0; i<256; i++) {
		hist[i] = log(1+hist[i]);
		if (max_idx<0 || hist[i]>hist[max_idx]) {
			max_idx = i;
		}
	}
	return max_idx;
}

